// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
#pragma once

#include <pollux/type/cpp_to_type.h>

namespace kumo::pollux {
    template<typename UNDERLYING_TYPE>
    struct Variadic {
        using underlying_type = UNDERLYING_TYPE;

        Variadic() = delete;
    };

    // A type that can be used in simple function to represent any type.
    // Two Generics with the same type variables should bound to the same type.
    template<size_t id>
    struct TypeVariable {
        static size_t getId() {
            return id;
        }
    };

    using T1 = TypeVariable<1>;
    using T2 = TypeVariable<2>;
    using T3 = TypeVariable<3>;
    using T4 = TypeVariable<4>;
    using T5 = TypeVariable<5>;
    using T6 = TypeVariable<6>;
    using T7 = TypeVariable<7>;
    using T8 = TypeVariable<8>;

    template<size_t id>
    struct IntegerVariable {
        static size_t getId() {
            return id;
        }

        static std::string name() {
            return fmt::format("i{}", id);
        }
    };

    using P1 = IntegerVariable<1>;
    using P2 = IntegerVariable<2>;
    using P3 = IntegerVariable<3>;
    using P4 = IntegerVariable<4>;
    using S1 = IntegerVariable<5>;
    using S2 = IntegerVariable<6>;
    using S3 = IntegerVariable<7>;
    using S4 = IntegerVariable<8>;

    template<typename P, typename S>
    struct ShortDecimal {
    private:
        ShortDecimal() {
        }
    };

    template<typename P, typename S>
    struct LongDecimal {
    private:
        LongDecimal() {
        }
    };

    struct AnyType {
    };

    template<typename T = AnyType, bool comparable = false, bool orderable = false>
    struct Generic {
        Generic() = delete;

        static_assert(!(orderable && !comparable), "Orderable implies comparable.");
    };

    using Any = Generic<>;

    template<typename T>
    using Comparable = Generic<T, true, false>;

    // Orderable implies comparable.
    template<typename T>
    using Orderable = Generic<T, true, true>;

    template<typename>
    struct isVariadicType : public std::false_type {
    };

    template<typename T>
    struct isVariadicType<Variadic<T> > : public std::true_type {
    };

    template<typename>
    struct isGenericType : public std::false_type {
    };

    template<typename T, bool comparable, bool orderable>
    struct isGenericType<Generic<T, comparable, orderable> >
            : public std::true_type {
    };

    template<typename>
    struct isOpaqueType : public std::false_type {
    };

    template<typename T>
    struct isOpaqueType<std::shared_ptr<T> > : public std::true_type {
    };

    template<typename KEY, typename VALUE>
    struct Map {
        using key_type = KEY;
        using value_type = VALUE;

        static_assert(
            !isVariadicType<key_type>::value,
            "Map keys cannot be Variadic");
        static_assert(
            !isVariadicType<value_type>::value,
            "Map values cannot be Variadic");

    private:
        Map() {
        }
    };

    template<typename ELEMENT>
    struct Array {
        using element_type = ELEMENT;

        static_assert(
            !isVariadicType<element_type>::value,
            "Array elements cannot be Variadic");

    private:
        Array() {
        }
    };

    template<typename ELEMENT>
    using ArrayWriterT = Array<ELEMENT>;

    template<typename... T>
    struct Row {
        template<size_t idx>
        using type_at = typename std::tuple_element<idx, std::tuple<T...> >::type;

        static const size_t size_ = sizeof...(T);

        static_assert(
            std::conjunction<std::bool_constant<!isVariadicType<T>::value>...>::value,
            "Struct fields cannot be Variadic");

    private:
        Row() {
        }
    };

    struct DynamicRow {
    private:
        DynamicRow() {
        }
    };

    // T must be a struct with T::type being a built-in type and T::typeName
    // type name to use in FunctionSignature.
    // providesCustomComparison must be set to true to ensure values are wrapped in
    // a view that exposes the custom comparison operations in Simple Functions.
    template<typename T, bool providesCustomComparison_ = false>
    struct CustomType {
        static constexpr bool providesCustomComparison = providesCustomComparison_;

    private:
        CustomType() {
        }
    };

    template<typename T>
    struct UnwrapCustomType {
        using type = T;
    };

    template<typename T, bool providesCustomComparison>
    struct UnwrapCustomType<CustomType<T, providesCustomComparison> > {
        using type = typename T::type;
    };

    template<typename T>
    struct providesCustomComparison {
        static constexpr bool value = false;
    };

    template<typename T>
    struct providesCustomComparison<CustomType<T, true> > {
        static constexpr bool value = true;
    };

    struct IntervalDayTime {
    private:
        IntervalDayTime() {
        }
    };

    struct IntervalYearMonth {
    private:
        IntervalYearMonth() {
        }
    };

    struct Date {
    private:
        Date() {
        }
    };

    struct Varbinary {
    private:
        Varbinary() {
        }
    };

    struct Varchar {
    private:
        Varchar() {
        }
    };

    template<typename T>
    struct Constant {
    };

    template<typename T>
    struct UnwrapConstantType {
        using type = T;
    };

    template<typename T>
    struct UnwrapConstantType<Constant<T> > {
        using type = T;
    };

    template<typename T>
    struct isConstantType {
        static constexpr bool value = false;
    };

    template<typename T>
    struct isConstantType<Constant<T> > {
        static constexpr bool value = true;
    };

    template<typename... TArgs>
    struct ConstantChecker {
        static constexpr bool isConstant[sizeof...(TArgs)] = {
            isConstantType<TArgs>::value...
        };
    };

    /// CppToType templates for types introduced above.

    template<>
    struct CppToType<Varchar> : public CppToTypeBase<TypeKind::VARCHAR> {
    };

    template<>
    struct CppToType<Varbinary> : public CppToTypeBase<TypeKind::VARBINARY> {
    };

    template<>
    struct CppToType<Date> : public CppToTypeBase<TypeKind::INTEGER> {
    };

    template<typename T>
    struct CppToType<Generic<T> > : public CppToTypeBase<TypeKind::UNKNOWN> {
    };

    template<typename KEY, typename VAL>
    struct CppToType<Map<KEY, VAL> > : public TypeTraits<TypeKind::MAP> {
        static auto create() {
            return MAP(CppToType<KEY>::create(), CppToType<VAL>::create());
        }
    };

    template<typename ELEMENT>
    struct CppToType<Array<ELEMENT> > : public TypeTraits<TypeKind::ARRAY> {
        static auto create() {
            return ARRAY(CppToType<ELEMENT>::create());
        }
    };

    template<typename... T>
    struct CppToType<Row<T...> > : public TypeTraits<TypeKind::ROW> {
        static auto create() {
            return ROW({CppToType<T>::create()...});
        }
    };

    template<>
    struct CppToType<DynamicRow> : public TypeTraits<TypeKind::ROW> {
        static std::shared_ptr<const Type> create() {
            throw std::logic_error{"can't determine exact type for DynamicRow"};
        }
    };

    template<typename T, bool providesCustomComparison>
    struct CppToType<CustomType<T, providesCustomComparison> >
            : public CppToType<typename T::type> {
        static auto create() {
            return CppToType<typename T::type>::create();
        }
    };

    /// SimpleTypeTrait template.

    template<typename P, typename S>
    struct SimpleTypeTrait<ShortDecimal<P, S> >
            : public TypeTraits<TypeKind::BIGINT> {
    };

    template<typename P, typename S>
    struct SimpleTypeTrait<LongDecimal<P, S> >
            : public TypeTraits<TypeKind::HUGEINT> {
    };

    template<>
    struct SimpleTypeTrait<Varchar> : public TypeTraits<TypeKind::VARCHAR> {
    };

    template<>
    struct SimpleTypeTrait<Varbinary> : public TypeTraits<TypeKind::VARBINARY> {
    };

    template<>
    struct SimpleTypeTrait<Date> : public SimpleTypeTrait<int32_t> {
        static constexpr const char *name = "DATE";
    };

    template<>
    struct SimpleTypeTrait<IntervalDayTime> : public SimpleTypeTrait<int64_t> {
        static constexpr const char *name = "INTERVAL DAY TO SECOND";
    };

    template<>
    struct SimpleTypeTrait<IntervalYearMonth> : public SimpleTypeTrait<int32_t> {
        static constexpr const char *name = "INTERVAL YEAR TO MONTH";
    };

    template<typename T, bool comparable, bool orderable>
    struct SimpleTypeTrait<Generic<T, comparable, orderable> > {
        static constexpr TypeKind typeKind = TypeKind::INVALID;
        static constexpr bool isPrimitiveType = false;
        static constexpr bool isFixedWidth = false;
    };

    template<typename T>
    struct SimpleTypeTrait<std::shared_ptr<T> >
            : public TypeTraits<TypeKind::OPAQUE> {
    };

    template<typename KEY, typename VAL>
    struct SimpleTypeTrait<Map<KEY, VAL> > : public TypeTraits<TypeKind::MAP> {
    };

    template<typename ELEMENT>
    struct SimpleTypeTrait<Array<ELEMENT> > : public TypeTraits<TypeKind::ARRAY> {
    };

    template<typename... T>
    struct SimpleTypeTrait<Row<T...> > : public TypeTraits<TypeKind::ROW> {
    };

    template<>
    struct SimpleTypeTrait<DynamicRow> : public TypeTraits<TypeKind::ROW> {
    };

    // T is also a simple type that represent the physical type of the custom type.
    template<typename T, bool providesCustomComparison>
    struct SimpleTypeTrait<CustomType<T, providesCustomComparison> >
            : public SimpleTypeTrait<typename T::type> {
        using physical_t = SimpleTypeTrait<typename T::type>;
        static constexpr TypeKind typeKind = physical_t::typeKind;
        static constexpr bool isPrimitiveType = physical_t::isPrimitiveType;
        static constexpr bool isFixedWidth = physical_t::isFixedWidth;

        // This is different than the physical type name.
        static constexpr char *name = T::typeName;
    };

    /// MaterializeType template.

    template<typename T>
    struct MaterializeType {
        using null_free_t = T;
        using nullable_t = T;
        static constexpr bool requiresMaterialization = false;
    };

    template<typename V>
    struct MaterializeType<Array<V> > {
        using null_free_t = std::vector<typename MaterializeType<V>::null_free_t>;
        using nullable_t =
        std::vector<std::optional<typename MaterializeType<V>::nullable_t> >;
        static constexpr bool requiresMaterialization = true;
    };

    template<typename K, typename V>
    struct MaterializeType<Map<K, V> > {
        using key_t = typename MaterializeType<K>::null_free_t;

        using nullable_t = melon::
        F14FastMap<key_t, std::optional<typename MaterializeType<V>::nullable_t> >;

        using null_free_t =
        melon::F14FastMap<key_t, typename MaterializeType<V>::null_free_t>;
        static constexpr bool requiresMaterialization = true;
    };

    template<typename... T>
    struct MaterializeType<Row<T...> > {
        using nullable_t =
        std::tuple<std::optional<typename MaterializeType<T>::nullable_t>...>;

        using null_free_t = std::tuple<typename MaterializeType<T>::null_free_t...>;
        static constexpr bool requiresMaterialization = true;
    };

    template<typename T>
    struct MaterializeType<std::shared_ptr<T> > {
        using nullable_t = T;
        using null_free_t = T;
        static constexpr bool requiresMaterialization = false;
    };

    template<typename T, bool providesCustomComparison>
    struct MaterializeType<CustomType<T, providesCustomComparison> > {
        using inner_materialize_t = MaterializeType<typename T::type>;
        using nullable_t = typename inner_materialize_t::nullable_t;
        using null_free_t = typename inner_materialize_t::null_free_t;
        static constexpr bool requiresMaterialization =
                inner_materialize_t::requiresMaterialization;
    };

    template<>
    struct MaterializeType<Varchar> {
        using nullable_t = std::string;
        using null_free_t = std::string;
        static constexpr bool requiresMaterialization = false;
    };

    template<>
    struct MaterializeType<Varbinary> {
        using nullable_t = std::string;
        using null_free_t = std::string;
        static constexpr bool requiresMaterialization = false;
    };
} // namespace kumo::pollux
